/**
 *       Copyright (c) 2012 Lukas Zaruba
 *
 *   This file is part of Robot Playground.
 *
 *   Robot Playground is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Robot Playground is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with Robot Playground. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zarubsys.commons.scripting.utils;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;

import net.zarubsys.commons.scripting.IScriptFunction;
import net.zarubsys.commons.scripting.Script;
import net.zarubsys.commons.scripting.ScriptingEngine;
import net.zarubsys.commons.scripting.ScriptingEnvironment;
import net.zarubsys.commons.scripting.ScriptingException;
import net.zarubsys.commons.scripting.functions.internal.DumpFunction;

import org.apache.log4j.Logger;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;

/**
 * Based on scripting language from Media Solutions Slovakia, a.s.
 * @author Lukas Zaruba, lukas.zaruba@gmail.com
 */
public class ScriptHelper {
	
	// TODO add parsing from text (caching?)
	
	private static final String EXT_POINT_ID = "net.zarubsys.commons.scripting.scriptFunctions";
	private static final Logger log = Logger.getLogger(ScriptHelper.class);
	
	private final Map<String, ScriptCacheRecord> cache = new HashMap<String, ScriptCacheRecord>();
	private final Map<String, IScriptFunction> functions = new HashMap<String, IScriptFunction>();
	
	private static final ScriptHelper INSTANCE = new ScriptHelper();
	
	private ScriptHelper() {
		IConfigurationElement[] elems = Platform.getExtensionRegistry().getConfigurationElementsFor(EXT_POINT_ID);
		for (IConfigurationElement elem : elems) {
			IScriptFunction script;
			try {
				script = (IScriptFunction) elem.createExecutableExtension("class");
			} catch (CoreException e) {
				log.error("Cannot instantiate script function!", e);
				continue;
			}
			functions.put(script.getName(), script);
		}
	}
	
	public static final ScriptHelper getInstance() {
		return INSTANCE;
	}
	
	public IScriptFunction getFunction(String name) {
		return functions.get(name);
	}
	
	public Script parseScriptFromFile(File file) throws ScriptingException {
		Script script = null;
        try {
            try (Reader r = new FileReader(file)) {
                script = ScriptingEngine.parseScript(r, file.getAbsolutePath());
            }
        } catch (IOException e) {
            throw new RuntimeException("Unable to parse script from file " + file.getAbsolutePath(), e);
        }
		return script;
	}
	
	public Script getScript(String path) throws ScriptingException {
//		path = CFGVariablesUtils.getInstance().expandVariable(path); // TODO how to resolve paths?
		File file = new File(path);
		ScriptCacheRecord record = cache.get(path);
		if (record != null && file.lastModified() != record.getDateOfModification()) {
			cache.remove(path);
			record = null;
		}
		
		if (record == null) {
			synchronized (this) {
				record = cache.get(path);
				if (record != null) return record.getScript();
				Script script = getScriptInternal(path);
				record = new ScriptCacheRecord(script, file.lastModified());
				cache.put(path, record);
			}
		}
		
		return record.getScript();
	}
	
	private Script getScriptInternal(String path) throws ScriptingException {
		Script script = ScriptingEngine.parseScript(new File(path));
		return script;
	}

	private static class ScriptCacheRecord {
		
		private final Script script;
		private final long lastModified;
		
		public ScriptCacheRecord(Script script, long lastModified) {
			this.script = script;
			this.lastModified = lastModified;
		}
	
		public Script getScript() {
			return script;
		}
	
		public long getDateOfModification() {
			return lastModified;
		}
		
	}
	
	/**
	 * Logs current scripting enviroment (similar as dump() function within script).
	 * Primary usage of this method is aimed for catch blocks of script running code, so that
	 * dump is created in case of any exception and not only when script calls for it.
	 * @param env - scripting enviroment, if null it doesn't throw usual NullPointerException 
	 * due to target usage of method and ugly double catch statement in such case. 
	 */
	public void logDump(ScriptingEnvironment env) {
		if (env == null) {
			log.error("Unable to log dump, because scripting enviroment is null!");	
			return;
		}
		try {
			new DumpFunction().execute(env, null);
		} catch (ScriptingException e) {
			log.error("Unable to log dump!", e);
		}
	}

}

